// Dustin Escoffery
// Copyright 2010
#include "hook_ddraw.h"
#define MakePtr(cast, ptr, addValue) (cast)((DWORD)(ptr)+(DWORD)(addValue))

static HWND bwWnd = NULL;
static LPDIRECTDRAWCLIPPER lpClipper = NULL;
static LPDIRECTDRAWSURFACE2 lpDDSBack = NULL;
static LPDIRECTDRAWSURFACE2 lpDDSPrimary = NULL;

// Release
const int OFFSET_REL = 2 * 4;
typedef HRESULT (WINAPI *DDREL)(LPDIRECTDRAW2);
static DDREL DD_Release;

// CreateSurface
const int OFFSET_CS = 6 * 4;
typedef HRESULT (WINAPI *DDCS)(LPDIRECTDRAW2,
   LPDDSURFACEDESC, LPDIRECTDRAWSURFACE2 FAR*, IUnknown FAR*);
static DDCS DD_CreateSurface;

// SetCooperativeLevel
const int OFFSET_SCL = 20 * 4;
typedef HRESULT (WINAPI *DDSCL)(LPDIRECTDRAW2,
   HWND, DWORD);
static DDSCL DD_SetCooperativeLevel;

// Unlock
const int OFFSET_UNL = 32 * 4;
typedef HRESULT (WINAPI *DDUNL)(LPDIRECTDRAWSURFACE2, LPVOID);
static DDUNL DD_Unlock;

// SetDisplayMode
const int OFFSET_SDM = 21 * 4;

// Do not let BW change the resolution
HRESULT WINAPI MySetDisplayMode(LPDIRECTDRAW2 lpDD,
   DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate,
   DWORD dwFlags)
{
   return DD_OK;
}

// Run directx in normal (windowed) mode
HRESULT WINAPI MySetCooperativeLevel(LPDIRECTDRAW2 lpDD,
   HWND hWnd, DWORD dwFlags)
{
   bwWnd = hWnd;
   lpClipper->SetHWnd(0, hWnd);
   return DD_SetCooperativeLevel(lpDD, hWnd, DDSCL_NORMAL);
}

// Render the Frame to Window
void Redraw()
{
   if (lpDDSPrimary && lpDDSBack)
   {
      POINT p;
      p.x = 0; p.y = 0;
      ClientToScreen(bwWnd, &p);

      RECT rcRectSrc, rcRectDest;
      GetClientRect(bwWnd, &rcRectDest);
      OffsetRect(&rcRectDest, p.x, p.y);
      SetRect(&rcRectSrc, 0, 0, 640, 480);
      lpDDSPrimary->Blt(&rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
   }
}

// Copy region to "backbuffer" frame
HRESULT WINAPI MyUnlock(LPDIRECTDRAWSURFACE2 lpDDS, LPVOID lpSurfaceData)
{
   HRESULT retval = DD_Unlock(lpDDS, lpSurfaceData);

   // GDI Copy
   HDC hdc, hdcBack;
   lpDDS->GetDC(&hdc);
   lpDDSBack->GetDC(&hdcBack);
   BitBlt(hdcBack, 0, 0, 640, 480, hdc, 0, 0, SRCCOPY);
   lpDDS->ReleaseDC(hdc);
   lpDDSBack->ReleaseDC(hdcBack);

   // Signal a redraw (flip)
   Redraw();
   return retval;
}

HRESULT WINAPI MyCreateSurface(LPDIRECTDRAW2 lpDD, LPDDSURFACEDESC lpDDSurfaceDesc,
   LPDIRECTDRAWSURFACE2 FAR *lplpDDSurface, IUnknown FAR *pUnkOuter)
{
   // The primary is the only surface in BW
   bool primary = (lpDDSurfaceDesc->ddsCaps.dwCaps == DDSCAPS_PRIMARYSURFACE);
   if (primary)
   {
      // Create a primary surface
      DD_CreateSurface(lpDD, lpDDSurfaceDesc, &lpDDSPrimary, pUnkOuter);
      lpDDSPrimary->SetClipper(lpClipper);

      // Create a "backbuffer"
      lpDDSurfaceDesc->dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
      lpDDSurfaceDesc->ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
      lpDDSurfaceDesc->dwWidth = 640;
      lpDDSurfaceDesc->dwHeight = 480;
      DD_CreateSurface(lpDD, lpDDSurfaceDesc, &lpDDSBack, pUnkOuter);

      // Create this surface in 8bpp-indexed mode
      lpDDSurfaceDesc->dwFlags |= DDSD_PIXELFORMAT;
      lpDDSurfaceDesc->ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
      memset(&lpDDSurfaceDesc->ddpfPixelFormat, 0, sizeof(DDPIXELFORMAT));
      lpDDSurfaceDesc->ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
      lpDDSurfaceDesc->ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8;
      lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount = 8;
   }
   HRESULT retval = DD_CreateSurface(lpDD, lpDDSurfaceDesc, lplpDDSurface, pUnkOuter);
   
   if (primary)
   {
      // Get Virtual Function Table
      DWORD* ppvtbl = (DWORD*)*lplpDDSurface;
      DWORD* pvtbl = (DWORD*)*ppvtbl;

      // Hook Unlock
      DWORD* pUnlock = MakePtr(DWORD*, pvtbl, OFFSET_UNL);
      DD_Unlock = (DDUNL)*pUnlock;
      *pUnlock = (DWORD)MyUnlock;
   }
   return retval;
}

// Release the extra stuff we added
ULONG WINAPI MyRelease(LPDIRECTDRAW2 lpDD)
{
   if (lpClipper) lpClipper->Release();
   lpClipper = NULL;
   if (lpDDSBack) lpDDSBack->Release();
   lpDDSBack = NULL;
   if (lpDDSPrimary) lpDDSPrimary->Release();
   lpDDSPrimary = NULL;
   return DD_Release(lpDD);
}

// Hook the DirectX 2 Interface
void HookDX(LPDIRECTDRAW *lplpDD)
{
   // Create a Clipper
   ((LPDIRECTDRAW2)*lplpDD)->CreateClipper(0, &lpClipper, NULL);

   // Get Virtual Function Table
   DWORD* ppvtbl = (DWORD*)*lplpDD;
   DWORD* pvtbl = (DWORD*)*ppvtbl;

   // Hook Release
   DWORD* pRelease = MakePtr(DWORD*, pvtbl, OFFSET_REL);
   DD_Release = (DDREL)*pRelease;
   *pRelease = (DWORD)MyRelease;

   // Hook Create Surface
   DWORD* pCreateSurface = MakePtr(DWORD*, pvtbl, OFFSET_CS);
   DD_CreateSurface = (DDCS)*pCreateSurface;
   *pCreateSurface = (DWORD)MyCreateSurface;

   // Hook Set Cooperative Level
   DWORD* pSetCooperativeLevel = MakePtr(DWORD*, pvtbl, OFFSET_SCL);
   DD_SetCooperativeLevel = (DDSCL)*pSetCooperativeLevel;
   *pSetCooperativeLevel = (DWORD)MySetCooperativeLevel;

   // Hook Set Display Mode
   DWORD* pSetDisplayMode = MakePtr(DWORD*, pvtbl, OFFSET_SDM);
   *pSetDisplayMode = (DWORD)MySetDisplayMode;
}

// Hook Entrypoint
HRESULT WINAPI MyDirectDrawCreate(GUID FAR *lpGUID,
   LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter)
{
   HRESULT retval = DirectDrawCreate(lpGUID, lplpDD, pUnkOuter);
   HookDX(lplpDD);
   return retval;
}
